package cn.ac.iie.kgmanager.dao;

import cn.ac.iie.kgmanager.domain.Relation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.neo4j.driver.v1.*;
import org.springframework.stereotype.Component;

import java.util.*;

@Slf4j
@AllArgsConstructor
@Component
public class RelationDao {
    private final Driver driver;


    /**
     * 新增关系
     *
     * @param startId 关系开始节点id
     * @param endId   关系结束节点id
     * @param type    关系类型
     * @return 操作状态
     */
    public boolean insertRelation(String startId, String endId, String type, Map<String, Object> relationPropertie) {
        StringBuilder sbCypher = new StringBuilder();
        // 拼接cypher
        sbCypher.append("match (n),(m) where n.id='").append(startId).append("' and m.id='").append(endId).append("' ");
        // 创建连接
        sbCypher.append("create (n)-");
        // 拼接连接类型
        sbCypher.append("[r");
        if (StringUtils.isNotBlank(type)) {
            sbCypher.append(":").append(type);
        }
        // 拼接属性
        sbCypher.append("{");

        List<String> temp = new ArrayList<>();
        Set<Map.Entry<String, Object>> entries = relationPropertie.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            if (entry.getValue() instanceof String) {
                temp.add(entry.getKey() + ":'" + entry.getValue() + "'");
            } else {
                temp.add(entry.getKey() + ":" + entry.getValue());
            }
        }

        sbCypher.append(StringUtils.join(temp, ","));
        sbCypher.append("}");

        sbCypher.append("]->(m)");

        String cypher = sbCypher.toString();
        log.info(cypher);
        return runCypher(cypher);
    }

    /**
     * 根据生成的实体id删除实体
     *
     * @param id 实体id
     * @return 删除关系是否成功
     */
    public boolean deleteById(String id) {
        Session session = driver.session();
        try {
            // match ()-[r]-() where r.id='' delete r

            String cypher = "match ()-[r]-() where r.id='" + id + "' delete r";
            log.info(cypher);

            session.run(cypher);
            return true;
        } catch (Exception e) {
            log.error("删除关系失败：{}", e);
            return false;
        }
    }


    /**
     * 根据生成的实体id删除实体
     *
     * @param ids 实体id列表
     * @return 删除关系是否成功
     */
    public boolean batchDeleteByIds(String[] ids) {
        Session session = driver.session();
        try {
            // match ()-[r]-() where r.id in ['s','sds'] delete r
            StringBuffer sbCypher = new StringBuffer();
            sbCypher.append("match ()-[r]-() where r.id in ");
            sbCypher.append("['").append(String.join("','", ids)).append("']");
            sbCypher.append(" delete r");
            String cypher = sbCypher.toString();
            log.info(cypher);

            session.run(cypher);
            return true;
        } catch (Exception e) {
            log.error("批量删除关系失败：{}", e);
            return false;
        }
    }

    /**
     * 根据条件查询关系
     *
     * @param target             目标节点id
     * @param source             开始节点id
     * @param relationType       关系类型(现在未使用)
     * @param relationProperties 关系属性
     * @param pageNum            查询页数
     * @param pageSize           每页数量
     * @param depth              查询深度
     * @return java.util.List<org.neo4j.driver.v1.Value>
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 9:56
     */
    public List<Value> findRelationByCondition(String target, String source, String relationType,
                                               Map<String, Object> relationProperties, Integer pageNum, Integer pageSize,
                                               Integer depth) {

        try (Session session = driver.session()) {
            // match (n)-[1..2]->(m) where n.id='' and m.id='' and r.a='A' return p skip
            // 拼接cypher
            String cypher = generateSearchCypher(target, source, relationType, relationProperties, pageNum,
                    pageSize, "search", depth);

            log.info(cypher);

            StatementResult sr = session.run(cypher);

            return getValues(sr);

        } catch (Exception e) {
            log.error("{}", e);
        }

        return null;
    }

    /**
     * 根据条件统计关系
     *
     * @param target             目标节点id
     * @param source             开始节点id
     * @param relationType       关系类型(现在未使用)
     * @param relationProperties 关系属性
     * @param depth              查询深度
     * @return 关系总数
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 9:56
     */
    public int countByCondition(String target, String source, String relationType, Map<String, Object> relationProperties,
                                Integer depth) {
        Session session = driver.session();

        try {
            // match (n)-[r:type]->(m) where n.id='' and m.id='' and r.a='A' return p skip
            // 拼接cypher
            String cypher = generateSearchCypher(target, source, relationType, relationProperties, 0,
                    0, "count", depth);

            StatementResult sr = session.run(cypher);
            Value value = Objects.requireNonNull(getValues(sr)).get(0);
            return value.asInt();
        } catch (Exception e) {
            log.error("{}", e);
        } finally {
            session.close();
        }

        return 0;
    }

    /**
     * 查询节点间的所有关系
     *
     * @param ids 节点id
     * @return java.util.List<org.neo4j.driver.v1.Value>
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 10:01
     */
    public List<Value> findAllRelationByNodes(String[] ids) {
        Session session = driver.session();
        StringBuffer sbCypher = new StringBuffer();

        try {

            sbCypher.append("match p=(n)-[r]->(m) where");
            sbCypher.append(" n.id in ['").append(String.join("','", ids)).append("']");
            sbCypher.append(" and");
            sbCypher.append(" m.id in ['").append(String.join("','", ids)).append("']");
            sbCypher.append(" return distinct p");
            log.info(sbCypher.toString());
            StatementResult sr = session.run(sbCypher.toString());
            return getValues(sr);

        } catch (Exception e) {
            log.error("{}", e);
        } finally {
            session.close();
        }

        return null;

    }

    /**
     * 生成cypher语句
     *
     * @param target             目标节点id
     * @param source             开始节点id
     * @param relationType       关系类型(现在未使用)
     * @param relationProperties 关系属性
     * @param pageNum            查询页数
     * @param pageSize           每页数量
     * @param depth              查询深度
     * @return cypher 语句
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 9:56
     */
    private String generateSearchCypher(String target, String source, String relationType,
                                        Map<String, Object> relationProperties, Integer pageNum, Integer pageSize,
                                        String type, Integer depth) {

        StringBuilder sbCypher = new StringBuilder();
        sbCypher.append("match p=(n)-").append("[*1..");
        sbCypher.append(depth);
//        if (StringUtils.isNotBlank(relationType)) {
//            sbCypher.append(":" + relationType);
//        }
        sbCypher.append("]");
        sbCypher.append("->(m) ");

        // 拼接条件
        sbCypher.append("where 1=1 ");

        if (StringUtils.isNotBlank(source)) {
            sbCypher.append("and n.id='").append(source).append("' ");
        }
        if (StringUtils.isNotBlank(target)) {
            sbCypher.append("and m.id='").append(target).append("' ");
        }

//        if (relationProperties != null && !relationProperties.isEmpty()) {
//            Set<String> keys = relationProperties.keySet();
//            for (String key : keys) {
//                if (!key.equals("input_time")) {
//                    if (relationProperties.get(key) instanceof String) {
//                        sbCypher.append("and r." + key + "='" + relationProperties.get(key) + "' ");
//                    } else {
//                        sbCypher.append("and r." + key + "=" + relationProperties.get(key) + " ");
//                    }
//                }
//            }
//
//        }

        if ("count".equals(type)) {
            // 拼接返回
            sbCypher.append("return count(p)");

        } else {
            sbCypher.append("return p");

            if (pageNum != null && pageSize != null && pageNum != 0 && pageSize != 0) {
                sbCypher.append(" skip ").append((pageNum - 1) * pageSize).append(" limit ").append(pageSize);
            }
        }
        return sbCypher.toString();

    }

    /**
     * 根据id查询关系
     *
     * @param id 关系id
     * @return org.neo4j.driver.v1.Value
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 10:04
     */
    public Value findRelationById(String id) {
        Session session = driver.session();
        try {
            // match ()-[r]-() where r.id='' delete r
            StringBuffer sbCypher = new StringBuffer();
            sbCypher.append("match p=()-[r]-() where r.id='").append(id).append("' return p");

            String cypher = sbCypher.toString();
            log.info(cypher);

            StatementResult result = session.run(cypher);

            List<Value> values = getValues(result);
            if (values == null) {
                return null;
            } else {
                return values.get(0);
            }

        } catch (Exception e) {
            log.error("{}", e);
        } finally {
            session.close();
        }
        return null;
    }

    /**
     * 更新关系
     *
     * @param relation 关系
     * @return 更新是否成功
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 10:05
     */
    public boolean updateRelation(Relation relation) {
        Session session = driver.session();
        Transaction transaction = session.beginTransaction();
        try {
            // match ()-[r]-() where r.id='' set r.a='' return r
            updateRelation(transaction, relation);
            transaction.success();
            return true;
        } catch (Exception e) {
            log.error("修改关系失败：{}", e);
            transaction.failure();
            return false;
        } finally {
            transaction.close();
            session.close();
        }
    }

    /**
     * 批量更新关系（事务控制）
     *
     * @param relations 所有关系
     * @return boolean 更新状态
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 10:06
     */
    public boolean batchUpdateRelation(List<Relation> relations) {
        Session session = driver.session();
        Transaction transaction = session.beginTransaction();
        try {
            // match ()-[r]-() where r.id='' set r.a='' return r
            for (Relation relation : relations) {
                updateRelation(transaction, relation);
            }
            transaction.success();
            return true;
        } catch (Exception e) {
            log.error("批量修改关系失败：{}", e);
            transaction.failure();
            return false;
        } finally {
            transaction.close();
            session.close();
        }
    }

    /**
     * 事务更新关系
     *
     * @param transaction 事务对象
     * @param relation    关系
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 10:07
     */
    private void updateRelation(Transaction transaction, Relation relation) {
        StringBuffer sbCypher = new StringBuffer();

        sbCypher.append("match ()-[r]-() where r.id = '" + relation.getId() + "' ");

        Map<String, Object> properties = relation.getProperties();
        if (relation.getProperties() != null) {
            for (Map.Entry<String, Object> entry : properties.entrySet()) {
                if (entry.getValue() instanceof String) {
                    sbCypher.append("set r.").append(entry.getKey()).append("='").append(entry.getValue()).append("' ");
                } else {
                    sbCypher.append("set r." + entry.getKey() + "=" + entry.getValue() + " ");
                }
            }
        }

        sbCypher.append("return r");

        String cypher = sbCypher.toString();
        log.info(cypher);
        transaction.run(cypher);
    }


    private List<Value> getValues(StatementResult sr) {
        List<Value> valueArrayList = new ArrayList<>();
        while (sr.hasNext()) {
            Record record = sr.next();
            List<Value> values = record.values();
            if (values != null && values.size() > 0) {
                valueArrayList.addAll(values);
            }
        }
        return valueArrayList;
    }

    /**
     * 执行cypher 语句
     *
     * @param cypher 语句
     * @return boolean 执行是否成功
     * @author WangXiaoyu
     * @createTime 2019/6/19 0019 上午 10:09
     */
    private boolean runCypher(String cypher) {
        try (Session session = driver.session()) {
            session.run(cypher);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

}
